/***********
TBD:    Factory.DOM.WindowManager
        proper implementation of drag n drop
        
**/


///////////////////////////////////////////////////////////////////
// included here for testing
function Factory() {};

Factory.isClass = function(theObj, objClass){
	return theObj && theObj.hasOwnProperty && theObj instanceof objClass;
};

Factory.getClass = function(theObj){
	return theObj && theObj.hasOwnProperty && theObj.getClass &&theObj.getClass();
};

Factory.obj = function(proto) {
    function ClassWrapper() {
		var obj = this;
		if ( !(obj instanceof ClassWrapper) ) {
			var F = function() {}, obj;
			F.prototype = ClassWrapper.prototype;
			obj = new F();
		}
		if (obj.init) obj.init.apply(obj, arguments);
		return obj;
    }
	
	// we'll provide some standard default methods.
    ClassWrapper.prototype.isClass = function(objClass){
		return this instanceof objClass;
    };
    ClassWrapper.prototype.getClass = function(){
		return this.constructor;
    };

    if (proto) {
        for (var f in proto) {
            proto.hasOwnProperty(f) && (ClassWrapper.prototype[f] = proto[f]);
        };
    }
    return ClassWrapper;
};
///////////////////////////////////////////////////

var cl = console.log, dl;

Factory.NameSpace = function(module) {
    if (Factory[module]) {
        // module exists already
        return false;
        
     } else {
        Factory[module] = function() {};
        return Factory[module];
    }
}

Factory.requires = function(module) {

    var subPath = module.split('.'),
      pathNode = Factory,
      s = 0;

    for (; s < subPath.length; s++) {
        if ( pathNode[subPath[s]] ) {
            pathNode = pathNode[subPath[s]];
            continue;
        }
        return null;
    }
    return pathNode;
}

Factory.NameSpace("Widgets");

Factory.NameSpace("Utility");
Factory.Utility.CONSTANTS = {
    TABLECONTAINER: 'TABLECONTAINER',
    TABLE: 'TABLE',
    TABLEHEADER: 'TABLEHEADER',
    TABLEBODY: 'TABLEBODY',
    TABLEROW: 'TABLEROW',
    TABLEFOOTER: 'TABLEFOOTER',
    TABLECAPTION: 'TABLECAPTION',
    SIMPLECONSOLEWINDOW: 'SIMPLECONSOLEWINDOW',
    SIMPLECONSOLELOGAREA: 'SIMPLECONSOLELOGAREA',
    DOMWINDOW: "DOMWINDOW",
    WINDOWFRAME: "WINDOWFRAME",
    WINDOWBANNER: 'WINDOWBANNER',
    FRAMECONTENT: "WINDOWCONTENT",
    
    DEFAULTCONSOLE: null
};

Factory.Utility.bindContext = function(theFunc, toContext) {
    return function() {
        // return whatever the function returns
        return theFunc.apply(toContext, arguments);
    };
};
Factory.Utility.bindContext2 = function(theFunc, toContext, retContext) {
    return function() {
        var fContext = theFunc.apply(toContext, arguments);
        
        // return retContext if given else return what our function returned.
        return retContext ? retContext : fContext;
    };
};
Factory.Utility.bindContextWithArguments = function(theFunc, toContext, retContext /*, possible arguments for theFunc */) {
    // we need the 1st 3 args to this func to be - theFunc, toContext, retContext
    //
    var argArray = Array.prototype.slice.call(args).splice(0, 3);     // convert to array & remove the 1st 3 args
    
    return function() {
        var fContext = theFunc.apply(toContext, argArray);
        
        // return retContext if given else return what our function returned.
        return retContext ? retContext : fContext;
    };
};

Factory.Utility.deepCopy = function(src, dest) {
    if ( !(src instanceof Object) ) return src;

    return ( function _deepCopy(src, dest) {
        var dst = dest ? dest : src instanceof Array ? [] : {}; // if dest is not present then create a new object of type src
        
        for (var prop in src) {
            if (typeof src[prop] == "object" && src[prop] != null) {
                dst[prop] = src[prop] instanceof Array ? [] : {};
                _deepCopy(src[prop], dst[prop]);
            }
            else {
               dst[prop] = src[prop];
            }
        }
        return dst;
    } )(src, dest);
};

Factory.Utility.guid = ( function() {
    // unique ID generator for any arbitrary type of object
    // Typcally used if an id is required to identify any object
    //
    var uid = [],
     defaultType = 'ACME',
     seedInt = 1000;     // always reserve the 1st 1000 of any type

    return function(uidType) {
         var _uidType = uidType || _defaultType;

         uid[_uidType] = uid[_uidType] ? ++uid[_uidType] : seedInt;
         return ( [_uidType, '_', uid[_uidType]].join('') );
    };
} ) ()

Factory.Utility.len = function(obj) {
    // return number of sub-objects within the object
    // USAGE: used to count high-level items within an object
    //
     var n = 0;
     for (row in obj) {
       n++;
     }
     return n;
};

////////////////////////////////////////////
// Factory.Console - used for our console loging
Factory.NameSpace("Console");
Factory.Console.ColourCodedJsonString = function(obj) {
    var json = Factory.Console.filterCyclicReferences(obj),
        _circular = 'color:black; font-weight: bold;',
        _string = 'color:green',
        _number = 'color:darkorange',
        _boolean = 'color:blue',
        _null = 'color:magenta',
        _key = 'color:red';

    if (typeof json != 'string') {
        json = JSON.stringify(json, null, 2);
    } else {
        return '<span style="' + _string + '">' + json + '</span>';
    }

    json.replace(/(^\")(.*)(\")/, '$2');
    json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    
    return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
        var _match = match,
            style = _number;
        
        if ( /^"\[Circular\]"$/.test(match) ) {
            style = _circular;
            _match = _match.split(/"/)[1];  // remove the quotes
            
        } else if (/^"/.test(match)) {
            if (/:$/.test(match)) {
                style = _key;
                _match = _match.split(/"/)[1]+':';  // remove the quotes(") around the key string
            } else {
                style = _string;
            }
        } else if (/true|false/.test(match)) {
            style = _boolean;
        } else if (/null/.test(match)) {
            style = _null;
        }
        
        return '<span style="' + style + '">' + _match + '</span>';
        
    });
}; // Factory.Console.ColourCodedJsonString()


Factory.Console.filterCyclicReferences = function(src, dest) {
    if ( !(src instanceof Object) ) return src;
    var roots = [src];

    return ( function _filter(src, dest) {
        var dst = dest ? dest : src instanceof Array ? [] : {};

        for (var prop in src) {
            if (typeof src[prop] == "object") {
                if (roots.indexOf( src[prop] ) !== -1 ) {
                   dst[prop] = "[Circular]";
                   continue;
                }
                roots.push(src[prop]);
                dst[prop] = src[prop] instanceof Array ? [] : {};
                _filter(src[prop], dst[prop]);
                roots.pop()
            }
            else {
               dst[prop] = src[prop];
            }
        }
        return dst;
    } )(src, dest);
    
}; // Factory.Console.sanitiseCyclicObject()

Factory.Console.SimpleConsole = Factory.obj( {
    init: function( title ) {
    
        var wObject = Factory.WindowManager.WMObject.WindowFrame(title ? title : "My Console Window");
        this.logArea = wObject.getContentPane();                
    },
    
    log: function() {
        if (arguments) {
            var innerHtmlStr ='', e;
                
            for (var i = 0; i < arguments.length; i++) {
                innerHtmlStr = [innerHtmlStr, Factory.Console.ColourCodedJsonString(arguments[i])].join(' ');            
            }
            e = Factory.Dom.API.TextElement(innerHtmlStr);
            Factory.Dom.API.append(this.logArea, e);
        }
        
        //set scroll position to bottom/end of log
        this.logArea.scrollTop = this.logArea.scrollHeight;
        return this;
    },
    
    toggleDisplay: function(onOff) {
        var display = onOff ? "none" : "block";
        Factory.Dom.API.setStyleCSS(this.consoleWindow, "display", display);
        return this;
        
    }
    
} ); //Factory.Console.SimpleConsole()
//////////////////////

Factory.ProcessManager = Factory.obj( {
    init: function() {
        var _processQ = [];
    }
    
} ); // Factory.ProcessManager = Factory.obj()

/////////////////////////////////////////////////////////
Factory.NameSpace("XHR");
Factory.XHR.Channel = Factory.obj( {
    // the sequence of using an XHR object:
    //  1. initantiate an XMLHttpRequest object
    //  2. set XHR.onreadystatechange callback
    //  3. open the connection with the given URI
    //  4. send the request
    //
    
    // 1. instantiate XHR object
    init: function() {
        this.XHR = null;
        this.callback = null;
        this.response = null;
        
        try {
            //if window.XMLHttpRequest exists then we are using : IE7+, Firefox, Chrome, Opera, Safari
            // esle we assume: IE6, IE5
            //
            this.XHR = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
        } catch(e) {
            dl.log("XHR Error.. Unable to create request: ", e.toString());
            return;
        }

    }, //init()

    setResponseCallback: function(cb) {
        //
        // set the call back to handle the response; sets the this.response to the
        // returned responseText.  if no cb is specified then no further action is
        // taken.
        //
        this.callback = ( function(XHRObject, cb) {
            return ( function() {
                if (XHRObject.XHR.readyState != 4) {
                    dl.log('XHR Debug.. readyState: ', XHRObject.XHR.readyState);
                }
                else {
                    // reset busy cursor to default
                    // _DOM.update.style(domBody, { cursor: 'default'});
                    // read  the response if http status is 'OK'
                    if ( XHRObject.XHR.status == 200 ) {
                       try {
                            dl.log('XHR Debug.. Request status: 4 (complete)');
                            XHRObject.response = XHRObject.XHR.responseText;
                            if ( cb ) {
                                cb(XHRObject.XHR.responseText);		// process the response
                            }
                       }
                       catch(e) {
                         dl.log('XHR Error.. Reading the response:', e.toString());
                       }
                    }
                    else {
                       dl.log('XHR Error.. Retrieving the response:', XHRObject.XHR.statusText);
                       // reset busy cursor to default
                       // _DOM.update.style(domBody, { cursor: 'default'});
                    }
                  }
            } );    // return
            
        } )(this, cb);  // this.callback - bound to *this* and our callback function
        
        return this;
    },

    setTimeoutCallback:function(cb) {
    
        //this.XHR.ontimeout = .....
        return this;
    },
    
    openConnection: function(uri, type, aSync) {
        // type = 'GET' or 'POST'
        // aSync: True/False

        // Note: [from MDN] Calling the open method for an already active request (one for which
        // open()or openRequest()has already been called) is the equivalent of calling abort().

        aSync = !!aSync;
        type = type || 'GET';
        
        
        if (this.callback) {
            try {
                this.XHR.open(type, uri, aSync);
                this.XHR.onreadystatechange = this.callback;
                
            } catch(e) {
                dl.log("XHR Error.. Unable to open the connection: ", e.toString());
                this.XHR = null;
            }
        } else {
            dl.log("XHR Error.. Unable to open the connection: No response callback set");
        }
        
        return this;
    },
    
    sendRequest: function(content) {
        if ( this.XHR ) try {
            this.response = null;
            content ? this.XHR.send(content) : this.XHR.send();
        } catch(e) {
            dl.log("XHR Error.. Unable to send request: ", e.toString());
        }
        return this;
    },
    
    getResponse: function() {
        return this.response;   // will be set in the onreadystatechange callback when it completes
    },
    
    abort: function() {
        // this.XHR.abort();
        return this;
    }
    
    
} ); // Factory.XHR.Channel = Factory.obj()

/////////////////////

Factory.NameSpace("Dom");
Factory.Dom.API = {
    InitialiseDom: function() {
        Factory.Dom.API.ROOTDOCUMENTBODY = document.body;
        Factory.WindowManager.WMObject = new Factory.WindowManager.Manager();
        
        Factory.Utility.CONSTANTS.DEFAULTCONSOLE = dl = new Factory.Console.SimpleConsole("Root Console");
        dl.log("Factory.Dom.API> ", {a:1, 'b':'foo', c:[false,'false',null, 'null', {d:{e:1.3e5,f:'1.3e5'}}]} );
        
    }, // InitialiseDom()
    
    CreateByTag: function(tag, className, cssStyleStr, uidType) {
        var e = document.createElement(tag);
        if (uidType) e.id = Factory.Utility.guid(uidType);
        if (className) e.class = className;
        if (cssStyleStr) e.style.cssText = cssStyleStr;

        return e;
    },
    append: function(parent, child) {
        return parent.appendChild(child);
    },
    insertTableRow: function(e, rowIdx, className, cssStyleStr, uidType) {
        var f = e.insertRow(rowIdx);
        uidType && ( f.id = Factory.Utility.guid(uidType) );
        className && ( f.class = className );
        cssStyleStr && ( f.style.cssText = cssStyleStr );
        
        return f;
    },
    
    insertTableRowCell: function(e, colIdx, className, cssStyleStr, uidType) {
        var f = e.insertCell(colIdx);
        uidType && ( f.id = Factory.Utility.guid(uidType) );
        className && ( f.class = className );
        cssStyleStr && ( f.style.cssText = cssStyleStr );
        
        return f;
    },
    
    TextElement: function(str) {
        var e = Factory.Dom.API.CreateByTag("pre");
        e.innerHTML = str;
        
        return e;
    },
        
    setInnerHtml: function(e, htmlStr) {
        e.innerHTML = htmlStr;
        return e;
    },
    
    setStyleCSSstring: function(e, cssStyleString) {
        e.style.cssText = cssStyleString;
        return e;
    },
    
    setStyleCSS: function(e, cssStyle, value) {
        e.style[cssStyle] = value;
        return e;
    }
    
}; // Factory.Dom.API {}


////////////////////////////////////////////////////////////
// drag n drop adapted from www.youngpup.net
var zzz = 1000; /*******************/

Factory.Dom.API.DD = {
    obj: null,
    
    init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper)
    {
    
        var dd = Factory.Dom.API.DD;

        o.onmousedown	= Factory.Dom.API.DD.start;

        o.hmode			= bSwapHorzRef ? false : true ;
        o.vmode			= bSwapVertRef ? false : true ;

        o.root = oRoot && oRoot != null ? oRoot : o ;

        if (o.hmode  && isNaN(parseInt(o.root.style.left  ))) o.root.style.left   = "0px";
        if (o.vmode  && isNaN(parseInt(o.root.style.top   ))) o.root.style.top    = "0px";
        if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right  = "0px";
        if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px";

        o.minX	= typeof minX != 'undefined' ? minX : null;
        o.minY	= typeof minY != 'undefined' ? minY : null;
        o.maxX	= typeof maxX != 'undefined' ? maxX : null;
        o.maxY	= typeof maxY != 'undefined' ? maxY : null;

        o.xMapper = fXMapper ? fXMapper : null;
        o.yMapper = fYMapper ? fYMapper : null;

        o.root.onDragStart	= new Function();
        o.root.onDragEnd	= new Function();
        o.root.onDrag		= new Function();
    },

    start : function(e)
    {
        var o = Factory.Dom.API.DD.obj = this,
            dd = Factory.Dom.API.DD;
        
        o.root.style.zIndex = zzz++;        /***********/

        e = Factory.Dom.API.DD.fixE(e);
        var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
        var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
        o.root.onDragStart(x, y);

        o.lastMouseX	= e.clientX;
        o.lastMouseY	= e.clientY;

        if (o.hmode) {
            if (o.minX != null)	o.minMouseX	= e.clientX - x + o.minX;
            if (o.maxX != null)	o.maxMouseX	= o.minMouseX + o.maxX - o.minX;
        } else {
            if (o.minX != null) o.maxMouseX = -o.minX + e.clientX + x;
            if (o.maxX != null) o.minMouseX = -o.maxX + e.clientX + x;
        }

        if (o.vmode) {
            if (o.minY != null)	o.minMouseY	= e.clientY - y + o.minY;
            if (o.maxY != null)	o.maxMouseY	= o.minMouseY + o.maxY - o.minY;
        } else {
            if (o.minY != null) o.maxMouseY = -o.minY + e.clientY + y;
            if (o.maxY != null) o.minMouseY = -o.maxY + e.clientY + y;
        }

        var thisContext = this;
        document.onmousemove	= dd.drag;
        document.onmouseup		= dd.end;

        return false;
    },

    drag : function(e)
    {
        var dd = Factory.Dom.API.DD;
        
        e = dd.fixE(e);
        var o = dd.obj;

        var ey	= e.clientY;
        var ex	= e.clientX;
        var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
        var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
        var nx, ny;

        if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
        if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
        if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
        if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);

        nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
        ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));

        if (o.xMapper)		nx = o.xMapper(y)
        else if (o.yMapper)	ny = o.yMapper(x)

        dd.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
        dd.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
        dd.obj.lastMouseX	= ex;
        dd.obj.lastMouseY	= ey;

        dd.obj.root.onDrag(nx, ny);
        return false;
    },

    end : function()
    {
        var dd = Factory.Dom.API.DD;
        
        document.onmousemove = null;
        document.onmouseup   = null;
        dd.obj.root.onDragEnd(	parseInt(dd.obj.root.style[dd.obj.hmode ? "left" : "right"]), 
                                    parseInt(dd.obj.root.style[dd.obj.vmode ? "top" : "bottom"]));
        dd.obj = null;
    },

    fixE : function(e)
    {
        if (typeof e == 'undefined') e = window.event;
        if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
        if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
        return e;
    }
    
}; // Factory.Dom.API.DD{}
////////////////////////////////////////////////////
Factory.NameSpace('WindowManager');
Factory.WindowManager.Manager = Factory.obj( {
    init: function() {
    
        this.ROOTDOCUMENTBODY = document.body;
        this.maxZindex = 0;
        
        this.windowFrames = [];
        this.maxDepth = 0;
        
        this.dockBar = "**********";
    },
    
    WindowFrame: function( heading ) {
        // creates a window frame and registers it
        
        var frame = new Factory.WindowManager.FrameSkeleton( this.ROOTDOCUMENTBODY, heading );
        frame.setZindex(this.maxZindex++);

        this.windowFrames[frame.id] = frame;

        // register for drag n drop
        Factory.Dom.API.DD.init(frame.banner, frame.root);

        return frame;
    },

    popWindowToTop: function(wID) {
        this.windowFrames[wID].setZindex(this.maxZindex++);
        return this;
        
    },
    
    registerWindow: function(w) {
        // if user has created their own element and want it registered with the window manager
    },
    
    deleteWindow: function(w) {
    },
    
    clean: function() {
    },    
    
    toggleIcon: function(w) {
    },
    
    hide: function(w) {
    },
    
    unHide: function(w) {
    
    },
    
    setDepth: function(w, d) {
    },
    
    toFront: function(w) {
    },
    
    toBack: function(w) {
    }
    
} ); // Factory.WindowManager.Manager = Factory.obj()

Factory.WindowManager.FrameSkeleton = Factory.obj( {
    init: function( wmRootWindow, heading ) {
        // creates a bare bones window frame
        //
        /**********
        
        WINDOWFRAME
        +-----------------------------------------------------+
        | +-------------------------------------------------+ |
        | | FRAMEBANNER                                     | |
        | +-------------------------------------------------+ |
        | +-------------------------------------------------+ |
        | |                                                 | |
        | |                                                 | |
        | |         FRAMECONTENT                            | |
        | |                                                 | |
        | |                                                 | |
        | |                                                 | |
        | +-------------------------------------------------+ |
        +-----------------------------------------------------+

        **/
        
        var WINDOWFRAME = Factory.requires("Utility.CONSTANTS.WINDOWFRAME"),
            FRAMEBANNER = Factory.requires("Utility.CONSTANTS.WINDOWBANNER"),
            FRAMECONTENT = Factory.requires("Utility.CONSTANTS.FRAMECONTENT"),
            cssFrame = "position: absolute; background: rgba(255,255,255,1.0); clear: both; border: 2px solid black; left: 100px; top: 100px;",
            cssBanner = "margin:2px; padding:2px; color:white; background-color: navy; font-weight: bold; ",
            cssFont = "font: normal normal 12px Verdana, Geneva, Arial, Helvetica, sans-serif;",
            cssContentArea = "overflow:auto; resize: both; background-color: #F4F4F4; width: 300px; height: 200px;  min-width: 200px;min-height: 75px;",
            frame = {};
        
        this.id = Factory.Utility.guid("WMObject"); // useful if we ned the id when within this object
        this.root = Factory.Dom.API.CreateByTag("div", WINDOWFRAME, cssFrame+cssFont, WINDOWFRAME),
        this.banner = Factory.Dom.API.CreateByTag("div", FRAMEBANNER, cssBanner, FRAMEBANNER);
        this.banner.innerHTML = heading ? heading : this.root.id;
        
        // this will be the dom element which displays the window contents
        // the calling app will use this element to display its wares
        //
        this.contentPane = Factory.Dom.API.CreateByTag("div", FRAMECONTENT, cssContentArea, FRAMECONTENT);

        this.deleteButton = null;
        this.iconButton = null;
        
        this.display = false;
        this.depth = this.currentDepth;
        this.isAlive = true;
        
        Factory.Dom.API.append(this.root, this.banner);
        Factory.Dom.API.append(this.root, this.contentPane);
        Factory.Dom.API.append(wmRootWindow, this.root);        
    },
    
    getContentPane: function() {
        return this.contentPane;
    },
    
    getFrameID: function() {
        return this.id;
    },
    
    setZindex: function(d) {
        this.root.style.zIndex = d;
        
        return this;
    }
    
} ); // Factory.WindowManager.Frame = Factory.obj()

////////////////////////////////////////////////////

Factory.Dom.API.DOmSkeletonTable = Factory.obj( {
    init: function(tableLabel) {
    
        var STYLEDefaults = {   // css formatted style strings
            table: "table-layout: fixed; border-collapse: collapse; width: 100%; height: 100%;",            
            header: "background-color: black; border: 1px solid #CCC; text-decoration: underline; color: #fdfdfd; text-align: left;",
            headerRow: "",
            headerCell: "padding: 1px; border: 1px solid #CCC;",

            body: "height:100%; border: 1px solid #CCC; background-color: #dddddd;",
            bodyRow: "padding: 1px; textAlign: left;",
            bodyCell: "padding: 1px; border: 1px solid #CCC;",

            footer: "background-color: #dedede;",
            footerRow: "",
            footerCell: "border: 1px solid #CCC;"
        },

        constants = Factory.requires("Utility.CONSTANTS"),
        TABLE = constants.TABLE,
        TABLEHEADER = constants.TABLEHEADER,
        TABLEBODY = constants.TABLEBODY,
        TABLEROW = constants.TABLEROW,
        TABLEFOOTER = constants.TABLEFOOTER,
        TABLECAPTION = constants.TABLECAPTION,
        
        Dom = Factory.requires('Dom.API'),
        DomCreate = Dom.CreateByTag,
        insertTableRow = Dom.insertTableRow,
        domAppend = Dom.append;

        this.nCols = 0;
        this.nRows = 0;

        var wObject = Factory.WindowManager.WMObject.WindowFrame(tableLabel ? tableLabel : "Table Contents");
        this.domTableContainer = wObject.getContentPane();
        
        
        this.domTable = DomCreate('table', TABLE, STYLEDefaults.table, TABLE);
        this.domTableHeader = DomCreate('thead', TABLEHEADER, STYLEDefaults.header, TABLEHEADER);
        insertTableRow(this.domTableHeader, 0);
        this.domTableBody = DomCreate('tbody', TABLEBODY, STYLEDefaults.body, TABLEBODY);

        domAppend(this.domTableContainer, this.domTable);
        domAppend(this.domTable, this.domTableHeader);
        domAppend(this.domTable, this.domTableBody);
        
    }, //init()
    
    addColumn: function(iCol) {
        var cellCSS = "padding: 1px; border: 1px solid #CCC;";

        for (var iRow = 0, row; row = this.domTable.rows[iRow]; iRow++) {
            Factory.Dom.API.insertTableRowCell(row, iCol, null, cellCSS);
        }

        this.nCols++;
        return this;
        
    },
    
    addRow: function(iRow) {
        var TABLEROW = Factory.Utility.CONSTANTS.TABLEROW,
            bodyRowCSS = "padding: 1px; textAlign: left;",
            bodyCellCSS = "padding: 1px; border: 1px solid #CCC;",
            nCols = this.domTableHeader.rows[0].cells.length,
            e = Factory.Dom.API.insertTableRow(this.domTableBody, iRow, TABLEROW, bodyRowCSS, TABLEROW);
        
        // add columns (if defined) to the newly created row
        for (var iCol = 0; iCol < this.nCols; iCol++) {
            Factory.Dom.API.insertTableRowCell(e, iCol, null, bodyCellCSS);
        }
        
        this.nRows++;
        return this;
    },
    
    removeColumn: function(iCol) {
        // we'll need to delete the appropriate cell from every row
        // including from the header
        //
        var row,
            nRows = this.domTableHeader.rows.length + this.nCols;
        
        for (var iRow = 0; iRow < nRows; iRow++) {
            row = this.domTable.rows[iRow];
            row.deleteCell(iCol);
        }
        
        this.nCols--;
        return this;
    },
    
    removeRow:function(iRow) { 
        this.domTableBody.deleteRow(iRow);
        this.nRows--;
        return this;
    },
    
    getTableDOM: function() { return this.domTable; },
    getHeaderDOM: function() { return this.domTableHeader; },
    getBodyDOM: function() { return this.domTableBody; },
    getRowDOM: function(iRow) { },
    getCellDOM: function(iRow, iCol) { },
    
    getColumnHeading: function() { },
    getColumnHeadings:function() { },
    getData: function() { },
    getRowData: function() { },
    getCellData: function() { },
    getURL: function() {
        return this.URL;
    },
    
    
    setColumnHeading: function(iCol, header) {
        var cell = this.domTableHeader.rows[0].cells[iCol];
        cell.innerHTML = header;
    
    },
    setColumnHeadings:function(hArr) {
        // load the table header with the headings
        //
        var cellCSS = "padding: 1px; border: 1px solid #CCC;",
            nCols = this.nCols;

        // truncate the columns ??
        for (var iCol = hArr.length; iCol < nCols; iCol++) {
            this.removeColumn(iCol);
        }
        
        // expand number of columns ??
        for (var iCol = nCols; iCol < hArr.length; iCol++) {
            this.addColumn(iCol);
        }

        // we should have the appropriate number of columns now.
        //
        for (var iCol = 0; iCol < hArr.length; iCol++) {
            this.setColumnHeading(iCol, hArr[iCol]);
        }
        return this;

    },
    setTableStyle: function(s) {
        dl.log('in DOmSkeletonTable> style = ', s);
        return this;
    },
    setHeaderStyle: function() { },
    setBodyStyle: function() { },
    setData: function( tArr ) {
        // Notes: we size the table (ie. number of row elements according to the number
        // rows in the incomeing data array
        //

        var nCols = tArr[0].length,
            nRows = tArr.length,
            tCols = this.domTable.rows[0].cells.length,
            tRows = this.domTableBody.rows.length,
            i;
            
        
        // add or remove columns
        if (nCols > tCols) {
            // add columns
            for ( var i = tCols; i < nCols; i++ ) {
                this.addColumn(-1);
            }
        } else if ( nCols < tCols ) {
            // trim the table - remove columns
            for ( var i = nCols; i < tCols; i++ ) {
                this.removeColumn(-1);
            }
        }

        // add or remove rows
        if (nRows > tRows) {
            // add rows
            for ( var i = tRows; i < nRows; i++ ) {
                this.addRow(-1);
            }

        } else if ( nRows < tRows ) {
            // trim the table - remove rows
            for ( var i = nRows; i < tRows; i++ ) {
                this.removeRow(-1);
            }

        }
        
        // set the data
        for (var iRow = 0; iRow < tArr.length; iRow++) {
            this.setRowData(iRow, tArr[iRow]);
        }
        
        return this;
    },
    setRowData: function( iRow, data) {
        // assumes the number of elements in the array is equal to the number of columns

        var nCols = this.domTableHeader.rows[0].cells.length,
            row = this.domTableBody.rows[iRow];
            
        for (var iCol = 0, cell; cell = row.cells[iCol]; iCol++) {
            cell.innerHTML = data[iCol];
        }
        
        return this;
    },
    
    setColumnData: function(iCol, data) {
        // assumes the number of elements in the array is equal to the number of rows
        
        var trows = this.domTableBody.rows,
            nRows = trows.length;
            
        for (var iRow =0; iRow < nRows; iRow++) {
            trows[iRow].cells[iCol].innerHTML = data[iRow];
        }
        
        return this;
    },
    
    setURL: function(u) {
        this.URL = u;
        return this;
    },
    
    setRefreshFromUrlTimer: function(r) {
        this.refreshTime = r;
        //setTimeout...
        
        return this;
    }
    

} ); // Factory.Dom.API.DOmSkeletonTable()

( function(module) {


    var Widgets = Factory.requires('Widgets'),
        // Expect = module.requires('Expect'),
        Utility = Factory.requires('Utility'),
        bindContext = Utility.bindContext,
        bindContext2 = Utility.bindContext2,
        deepCopy = Utility.deepCopy,
        _handle;

    function DOMTable(obj) {
        // Expect(obj).to.contain.keys('columnHeadings', 'data');
     
        var tableDetails = {
            columnHeadings : obj && obj.columnHeadings ? deepCopy(obj.columnHeadings) : [],
            data:  obj && obj.data ? deepCopy(obj.data) : [],
            url:   obj && obj.url ? obj.url : null,
            nCols: obj && obj.columnHeadings ? obj.columnHeadings.length : false,
            nRows: obj && obj.data ? obj.data.length : false,
            dom: new Factory.Dom.API.DOmSkeletonTable()
        },
        domTable = tableDetails.dom,
        colHeadings = tableDetails.columnHeadings,
        darr = tableDetails.data,
        i;
        
        // for efficiency's sake, let's load the table if data is given
        // we'll use the direct interface to the dom table
        //
        domTable.setColumnHeadings(colHeadings);
        domTable.setData(darr);
        
        _handle = null; // used for the this.and object within TableObject.
        
        return new TableObject(tableDetails);
    }; // DOMTable()

    Widgets.Table = DOMTable;


    var tableTokens = {
        set: {
            table: {
                style: setTableStyle,
                header: {
                    style: setHeaderStyle,
                    titles: setColumnHeadings
                },
                data: setData,
                column: {
                    heading: setColumnHeading,
                    headings: setColumnHeadings,
                    data: setColumnData
                },
                row: { data: setRowData },
                cell: { data: setCellData },
                numRows: setNumRows,
                numColumns: setNumColumns,
                url: setURL
            },
            header: {
                style: setHeaderStyle,
                titles: setColumnHeadings
            },
            body: { style: setBodyStyle },
            column: {
                heading: setColumnHeading,
                data: setColumnData
            },
            row: { data: setRowData },
            cell: { data: setCellData },
            numRows: setNumRows,
            numColumns: setNumColumns,
            url: setURL
        },
        
        get: {
        },
        toggle:{
            draggable: {
                on: {},
                off: {}
            },
            display: {
                on: {},
                off: {}
            }
        },
        display: {
            at: { position: {} }
        }
    };

    function setTableStyle(s) {
        dl.log('in TableObject setTableStyle> ', s);
        this.dom.setTableStyle(s);
        return this;
    };
    function setHeaderStyle(s) {
        dl.log('setHeaderStyle> ', s);
        //console.log('setHeaderStyle> ', this.tokens);
        //this.and = new TableObject(this.obj);
        return this;
    };
    function setData( dArr ) {
        // load/initialise the table object with the data set
        // if we are re-initialising we need to clear out the old 
        //
        this.nCols = dArr[0].length;
        this.nRows = dArr.length;        
        this.data = deepCopy(dArr);
                
        this.dom.setData( dArr );
        return this;
    };
    function setColumnHeading() {};
    function setColumnHeadings() {};
    
    function setColumnData(iCol, cArr) {
        if (this.nRows != cArr.length) return this;
        // copy the data into the internal array
        for (var iRow = 0; iRow < this.nRows; iRow++) {
            this.data[iRow][iCol] = cArr[iRow]; // may need a deepcopy here in case cArr contains objects !
        }
        
        this.dom.setColumnData(iCol, cArr);
        return this;
    };
    function setRowData(iRow, rArr) {
        if (this.nCols != rArr.length) return this;
        // copy the data into the internal array
        deepCopy(rArr, this.data[iRow]);
        this.dom.setRowData(iRow, rArr);
        
        return this;
    };
    function setCellData(iRow, iCol, cData) {};
    function setNumRows() {};
    function setNumColumns() {};
    function setURL() {};
    function setBodyStyle() {};


    function TableObject(obj, token, parentContext, path) {
        var _obj = obj,
            FIRST,
            _path;
        
        this.tokens = {}; // we keep the array of set tokens here as we build the assertion string
        
        if (token) {
            // this context needs to observe the incoming parentContext tokens set
            this.tokens[token] = true;
            
            for (var t in parentContext.tokens) {
                parentContext.tokens.hasOwnProperty(t) &&
                    ( this.tokens[t] = parentContext.tokens[t] );
            }
             _path = path[token];
            //_obj['tokens'] = this.tokens;
                       
        } else {
            _path = tableTokens;
            _handle = this;     // the very first object in the chain.
        }
        
        // cl('1. _path = ', _path);
        
        this.and = _handle;
        FIRST = Object.keys(_path);
        
        if (FIRST) {
            var thisContext = this,
                i = 0,
                subContext,
                follow;
                
            // let's iterate through the symbols in the FIRST set
            for (i; i < FIRST.length; i++) {
                follow = FIRST[i];
                
                //cl('2. follow = ', follow);
                
                if (this.tokens[follow]) continue;  // recursion !!
                
                if ( typeof _path[follow] != 'function' ) {
                    subContext = new TableObject(_obj, follow, thisContext, _path);
                    this[follow] = subContext;
                } else {
                    this[follow] = bindContext2(_path[follow], _obj, parentContext);
                }
            }
        }
        
    }; // TableObject()

} )(Factory);
